/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.web.impl.mapping.AbstractMapping;
import net.paoding.rose.web.impl.mapping.Mapping;
import net.paoding.rose.web.impl.mapping.ModifiedMapping;
import net.paoding.rose.web.impl.module.Controller;
import net.paoding.rose.web.impl.module.Module;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class ModuleEngine implements Comparable<ModuleEngine> {

	private static Log logger = LogFactory.getLog(ModuleEngine.class);

	private final Module module;

	private final Mapping<ControllerEngine>[] controllerMappings;
	
	public ModuleEngine(Module module) {
		this.module = module;
		this.controllerMappings = init(module);
	}

	public Module getModule() {
		return module;
	}

	protected Mapping<ControllerEngine>[] init(Module module) {
		ArrayList<Mapping<ControllerEngine>> controllerBindings = new ArrayList<Mapping<ControllerEngine>>(module.getControllerMappings().size());
		Mapping<ControllerEngine> def = null;
		for (Mapping<Controller> mapping : module.getControllerMappings()) {
			Object controller = mapping.getMappingTarget().getControllerObject();
			Class<?> controllerClass = mapping.getMappingTarget().getControllerClass();
			ControllerEngine engine = new ControllerEngine(module, mapping.getPath(),
					controller, controllerClass);
			Mapping<ControllerEngine> modifiedMapping = new ModifiedMapping<ControllerEngine>(mapping, engine);
			controllerBindings.add(modifiedMapping);
			if (mapping.getPath().length() == 0) {
				def = null;
			} else if (mapping == module.getDefaultController()) {
				def = new DefaultControllerMapping<ControllerEngine>(modifiedMapping);
			}
		}
		int size = def == null ? controllerBindings.size() : controllerBindings.size() + 1;
		@SuppressWarnings("unchecked")
		Mapping<ControllerEngine>[] mappings = controllerBindings.toArray(new Mapping[size]);
		if (def != null) {
			mappings[size - 1] = def;
		}
		return mappings;
	}
	
	
	@SuppressWarnings("unchecked")
	public InvocationBean findInvocation(HttpServletRequest request, HttpServletResponse response, RequestPath requestPath) {
		String mappingPath = requestPath.getControllerPathInfo();
		String requestMethod = request.getMethod();
		int offset = 0;
		while (offset < this.controllerMappings.length) {
			Object[] matched = matched(mappingPath, requestMethod, offset);
			Mapping<ControllerEngine> mapping = (Mapping<ControllerEngine>) matched[0];
			MatchResult matchResult = (MatchResult) matched[1];
			offset = (Integer) matched[2];
			if (matchResult != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("matched(" + mappingPath + "): " + mapping);
					logger.debug("matchResult.matchedString= " + matchResult.getMatchedString());
				}
				//
				requestPath.setControllerPath(matchResult.getMatchedString());
				//
				matched = mapping.getMappingTarget().searchAction(request, response, requestPath);
				if (matched != null) {
					InvocationBean invocation = new InvocationBean();
					invocation.setRequest(request);
					invocation.setResponse(response);
					invocation.setRequestPath(requestPath);
					invocation.setModule(module);
					invocation.setControllerMatchResult(matchResult);
					invocation.setActionEngine(((Mapping<ActionEngine>) matched[0]).getMappingTarget());
					invocation.setActionMatchResult((MatchResult) matched[1]);
					return invocation;
				}
			}
		}
		return null;
		
	}

	protected Object[] matched(String path, String requestMethod, int offset) {
		if (logger.isDebugEnabled()) {
			logger.debug("try to find a controllerEngine for mappingPath [" + path + "]");
		}
		MatchResult matchResult = null;
		Mapping<ControllerEngine> mapping = null;
		for (; matchResult == null && offset < controllerMappings.length; offset++) {
			mapping = controllerMappings[offset];
			if (logger.isDebugEnabled()) {
				logger.debug("try matching(" + path + "): " + mapping);
			}
			matchResult = mapping.match(path, requestMethod);
		}
		return new Object[]{matchResult == null ? null : mapping, matchResult, offset};
	}
	

	@Override
	public int compareTo(ModuleEngine o) {
		return this.module.compareTo(o.getModule());
	}

	private class DefaultControllerMapping<T> extends AbstractMapping<T> {
		DefaultControllerMapping(Mapping<T> inner) {
			super(inner.getPath(), inner.getMethods(), inner.getMappingTarget());
		}

		@Override
		public MatchResult match(String path, String requestMethod) {
			return isRequestMethodSupported(requestMethod) ? new MatchResult("") : null;
		}

		@Override
		public int compareTo(Mapping<T> o) {
			return 1;
		}
	}
	

}
